package org.jflame.commons.crypto;

public class Transformation {

    public final static Transformation RSA_ECB_NOPADDING = new Transformation(Alg.RSA, OpMode.ECB, Padding.NoPadding);
    public final static Transformation RSA_ECB_PKCS1PADDING = new Transformation(Alg.RSA, OpMode.ECB,
            Padding.PKCS1Padding);
    public final static Transformation RSA_ECB_OAEP_SHA_MGF1PADDING = new Transformation(Alg.RSA, OpMode.ECB,
            Padding.OAEPWITHSHA256AndMGF1Padding);

    public final static Transformation AES_ECB_PKCS5PADDING = new Transformation(Alg.AES, OpMode.ECB,
            Padding.PKCS5Padding);
    public final static Transformation AES_CBC_PKCS5PADDING = new Transformation(Alg.AES, OpMode.CBC,
            Padding.PKCS5Padding);

    public final static Transformation SM4_ECB_PKCS7PADDING = new Transformation(Alg.SM4, OpMode.ECB,
            Padding.PKCS7Padding);
    public final static Transformation SM2_NONE_NOPADDING = new Transformation(Alg.SM2, null, null);

    public final static Transformation PBE_HMACSHA_AES_256 = new Transformation(Alg.PBEWithHmacSHA256AndAES_256, null,
            null);

    private Alg alg;
    private OpMode mode;
    private Padding padding;

    public Transformation(Alg _alg) {
        this(_alg, null, null);
    }

    public Transformation(Alg _alg, OpMode _mode, Padding _padding) {
        if (_alg == null) {
            throw new IllegalArgumentException("_alg not be null");
        }
        alg = _alg;
        if (_mode != null) {
            mode = _mode;
        } else {
            if (alg.name()
                    .startsWith("PBE")) {
                mode = OpMode.CBC;
            }
        }
        if (_padding != null) {
            padding = _padding;
        } else {
            if (alg.name()
                    .startsWith("PBE")) {
                padding = Padding.PKCS5Padding;
            }
        }
    }

    public Alg getAlg() {
        return alg;
    }

    public OpMode getMode() {
        return mode;
    }

    public Padding getPadding() {
        return padding;
    }

    /**
     * 是否需要向量
     * 
     * @return
     */
    public boolean isNeedIV() {
        return mode != null && (mode == OpMode.CBC || mode == OpMode.CFB || mode == OpMode.OFB || mode == OpMode.PCBC);
    }

    public boolean isSymmetric() {
        return this.alg.isSymmetric();
    }

    /**
     * 非对称加密算法
     * 
     * @return
     */
    public boolean isAsymmetric() {
        return this.alg.isAsymmetric();
    }

    @Override
    public String toString() {
        if (mode != null && padding != null) {
            return String.join("/", alg.name(), mode.name(), padding.name());
        }
        return alg.name();
    }

    /**
     * 根据文本实例化一个Transformation对象
     * 
     * @param transformationStr 如:AES/ECB/PKCS5PADDING
     * @return
     * @see Alg
     * @see OpMode
     * @see Padding
     */
    public static Transformation parse(String transformationStr) {
        String[] arr = transformationStr.split("/");
        Alg _alg = Alg.valueOf(arr[0]);
        OpMode _mode = null;
        Padding _padding = null;
        if (arr.length == 2) {
            _mode = OpMode.valueOf(arr[1]);
        } else if (arr.length > 2) {
            _mode = OpMode.valueOf(arr[1]);
            _padding = Padding.valueOf(arr[2]);
        }

        return new Transformation(_alg, _mode, _padding);
    }

    /**
     * 加密算法分组方式枚举
     * 
     * @author charles.zhang
     */
    public enum OpMode {
        /**
         * 电子密码本.将加密的数据分成若干组，每组的大小跟加密密钥长度相同.然后每组都用相同的算法和密钥加密<br>
         * 每次Key、明文、密文的长度都必须是64位,即需要填充
         */
        ECB,
        /**
         * 密码块链接. 每个明文块先与前一个密文块进行异或后，再进行加密,<b>需要初始向量IV</b>参与第一个块加密<br>
         * 每次Key、明文、密文的长度都必须是64位,即需要填充
         */
        CBC,
        /**
         * 加解密时，每个明文块不但要与上一个密文异或，还要与上一个明文进行异或,<b>需要初始向量IV</b>
         */
        PCBC,
        /**
         * 用前一个密文使用秘钥Key再加密后与明文异或得到密文.第一个密文<b>需要初始向量IV</b>加密得到. 解密也同样使用加密器进行解密,不需要填充
         */
        CFB,
        /**
         * 通过对IV向量加密后与明文进行异或得到密文，后面所有的加密都依赖于前一个加密后的输出(第一个是初始向量IV加密,这区别于CFB,它是依赖前一个密文),不需要填充
         */
        OFB,
        /**
         * 不需要填充
         */
        CTR,

        NONE
    }

    /**
     * 加密块填充模式枚举.<br>
     * 分组加密算法中如果明文字节码的长度不是块大小的整数倍，那么就需要在末尾进行填充
     */
    public enum Padding {
        /**
         * 一般用于RSA. 填充公式: 填充后的数据=00||BT||PS||00||D<br>
         * BT块类型, 取值为00 or 01(私钥运算时),取值为02(公钥运算时)<br>
         * PS填充字符串,长度为Len(EB)-3-Len(D),最少是8字节. BT=00PS为00,BT=01PS为FF,BT=02PS为伪随机生成，非零<br>
         * D明文数据
         */
        PKCS1Padding,
        /**
         * 用于块加密算法(如:des),分组大小为固定为8字节(64bit),填充数据是要填充的长度,如果刚好是整数位则再填充一个分组即8字节的0x08<br>
         */
        PKCS5Padding,
        /**
         * 用于块加密算法,分组大小可以是1到255字节,填充方式同PKCS5.如果加密操作的分组大小为8字节时，PKCS5同PKCS7填充结果一致,PKCS5可以理解为PKCS7的子集.
         */
        PKCS7Padding,
        NoPadding,
        /**
         * 填充数据为0×00,但最后一个字节为填充字节序列的长度
         */
        ISO10126Padding,
        /**
         * OAEPPadding与PKCS1Padding类似，但是数据加密前的编码方式不一
         */
        OAEPWithMD5AandMGF1Padding,
        OAEPWITHSHA256AndMGF1Padding
    }

}
